package service

import (
	"context"
	"errors"
	"file-download-server/dao"
	"file-download-server/util"
	"file-download-server/vo"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"
)

const (
	defaultTaskDir      = "upload"
	defaultDownloadDir  = "download"
	downloadTmpRoot     = "download_tmp"
	defaultDirPerm      = 0755
	defaultFilePerm     = 0644
	DefaultChunkSize    = 1024 * 1024
	chunkRetryAttempts  = 3
	chunkRetryBaseDelay = time.Second
	fsRetryAttempts     = 3
	fsRetryDelay        = 100 * time.Millisecond
	taskTypeUpload      = "upload"
	taskTypeDownload    = "download"
	taskManagerPoolSize = 5
	taskQueueSize       = taskManagerPoolSize * 4
)

var (
	ErrRangeNotSupported = errors.New("range not supported")
	ErrTaskNotFound      = errors.New("task not found")
	ErrFileNotFound      = errors.New("file not found")
	ErrInvalidParams     = errors.New("invalid parameters")
)

// FileService 提供文件上传、下载（本地/远程）、任务管理等功能的统一服务实现。
// 支持分片处理、并发执行、断点续传、失败重试以及任务取消等高级功能。
type FileService struct {
	taskDir  string                       // 默认任务文件存放目录（如 upload）
	mu       sync.Mutex                   // 保护任务修改的互斥锁
	cancelMu sync.RWMutex                 // 任务取消函数映射的读写锁
	cancels  map[int64]context.CancelFunc // 保存任务ID与其取消方法的映射
}

// NewFileService 创建一个新的文件服务实例，并初始化上传目录及任务取消映射。
// 返回：*FileService 新建的文件服务对象。
func NewFileService() *FileService {
	dir := defaultTaskDir
	_ = os.MkdirAll(dir, defaultDirPerm) // 初始化上传目录
	return &FileService{
		taskDir: dir,
		cancels: make(map[int64]context.CancelFunc),
	}
}

// RegisterTaskCancel 为任务注册取消函数。
// 调用此方法后，可以通过 CancelTask 停止对应任务。
func (s *FileService) registerCancel(taskID int64, cancel context.CancelFunc) {
	s.cancelMu.Lock()
	defer s.cancelMu.Unlock()
	s.cancels[taskID] = cancel
}

// UnregisterTaskCancel 注销任务的取消函数。
// 移除任务ID与取消函数的绑定，释放资源。
func (s *FileService) unregisterCancel(taskID int64) {
	s.cancelMu.Lock()
	defer s.cancelMu.Unlock()
	delete(s.cancels, taskID)
}

// CancelTask 主动取消正在执行的任务。
func (s *FileService) cancelTask(taskID int64) {
	s.cancelMu.Lock()
	cancel, ok := s.cancels[taskID]
	if ok {
		delete(s.cancels, taskID)
	}
	s.cancelMu.Unlock()
	if ok {
		cancel()
	}
}

// DeleteFile 删除文件（带重试）。
func deleteFile(path string) {
	if path == "" {
		return
	}

	for i := 0; i < fsRetryAttempts; i++ {
		if err := util.DeleteFile(path); err == nil {
			return
		} else if i == fsRetryAttempts-1 {
			log.Printf("[WARN] 删除文件失败：%s err=%v\n", path, err)
		} else {
			time.Sleep(fsRetryDelay)
		}
	}
}

// RemoveDirWithRetry 删除目录（带重试）。
// 最多尝试 3 次；每次失败休眠 100ms。
func removeAllWithRetry(path string) {
	if path == "" {
		return
	}

	for i := 0; i < fsRetryAttempts; i++ {
		if err := os.RemoveAll(path); err == nil {
			return
		} else if i == fsRetryAttempts-1 {
			log.Printf("[WARN] 删除目录失败：%s err=%v\n", path, err)
		} else {
			time.Sleep(fsRetryDelay)
		}
	}
}

// CleanupUploadFiles 清理上传任务产生的所有分片和最终文件。
func (s *FileService) CleanupUploadFiles(task *vo.FileTaskRequest) {
	for i := 0; i < task.TotalChunks; i++ {
		partPath := filepath.Join(s.taskDir, fmt.Sprintf("%s.part%d", task.FileName, i))
		deleteFile(partPath)
	}
	deleteFile(filepath.Join(s.taskDir, task.FileName))
}

// CleanupDownloadFiles 清理下载任务产生的临时目录、分片以及本地保存的文件。
func (s *FileService) CleanupDownloadFiles(task *vo.FileTaskRequest) {
	partDir := filepath.Join(s.taskDir, downloadTmpRoot, task.FileName)
	removeAllWithRetry(partDir)

	for i := 0; i < task.TotalChunks; i++ {
		partPath := filepath.Join(defaultDownloadDir, fmt.Sprintf("%s.part%d", task.FileName, i))
		deleteFile(partPath)
	}

	if task.LocalPath != "" {
		deleteFile(task.LocalPath)
	}
}

// UploadFile 文件上传（支持分片 + 并发 + 断点续传 + 自动重试）
func (s *FileService) UploadFile(filePath string, chunkSize int) (*vo.FileTaskRequest, error) {
	if !util.FileExists(filePath) {
		return nil, fmt.Errorf("%w: %s", ErrFileNotFound, filePath)
	}

	fileName := filepath.Base(filePath)
	task := &vo.FileTaskRequest{
		FileName: fileName,
		FileType: taskTypeUpload,
		Status:   "pending",
	}
	task.TaskID = dao.FileTaskDB.CreateTask(task)

	ctx, cancel := context.WithCancel(context.Background())
	s.registerCancel(task.TaskID, cancel)
	defer func() {
		cancel()
		s.unregisterCancel(task.TaskID)
	}()

	lock := dao.FileTaskDB.GetLock(task.TaskID)
	lock.Lock()
	defer lock.Unlock()

	f, err := os.Open(filePath)
	if err != nil {
		task.Status = "failed"
		task.Message = fmt.Sprintf("打开文件失败: %v", err)
		return nil, err
	}
	defer f.Close()

	stat, err := f.Stat()
	if err != nil {
		task.Status = "failed"
		task.Message = fmt.Sprintf("读取文件信息失败: %v", err)
		return nil, err
	}

	fileSize := stat.Size()
	totalChunks := int((fileSize + int64(chunkSize) - 1) / int64(chunkSize))
	task.TotalChunks = totalChunks

	if err := s.uploadChunks(ctx, task, f, fileSize, chunkSize); err != nil {
		if errors.Is(err, context.Canceled) {
			task.Status = "cancelled"
			task.Message = "任务已被取消"
		} else {
			task.Status = "failed"
			task.Message = fmt.Sprintf("分片上传失败: %v", err)
		}
		return nil, err
	}

	finalPath := filepath.Join(s.taskDir, fileName)
	partFn := func(idx int) string {
		return filepath.Join(s.taskDir, fmt.Sprintf("%s.part%d", fileName, idx))
	}
	if err := mergePartFiles(task.TotalChunks, partFn, finalPath, true); err != nil {
		task.Status = "failed"
		task.Message = "合并失败"
		return nil, err
	}

	task.Status = "success"
	task.Message = "上传完成"
	log.Printf("[UPLOAD] 文件 %s 上传完成并合并成功\n", fileName)
	return task, nil
}

// uploadChunks 使用 TaskManager 并发上传分片（带重试）
func (s *FileService) uploadChunks(ctx context.Context, task *vo.FileTaskRequest, f *os.File, fileSize int64, chunkSize int) error {
	manager := GetTaskManager()
	var wg sync.WaitGroup
	var mu sync.Mutex
	var writeErr error

	totalChunks := task.TotalChunks
	for i := 0; i < totalChunks; i++ {
		if ctx.Err() != nil {
			break
		}
		idx := i
		bytesToRead := chunkSize
		if idx == totalChunks-1 {
			remaining := int(fileSize - int64(idx)*int64(chunkSize))
			if remaining > 0 && remaining < bytesToRead {
				bytesToRead = remaining
			}
		}
		if bytesToRead <= 0 {
			continue
		}

		wg.Add(1)
		manager.RunTask(func() {
			defer wg.Done()
			if ctx.Err() != nil {
				return
			}
			retries := chunkRetryAttempts
			var lastErr error

			for attempt := 1; attempt <= retries; attempt++ {
				if ctx.Err() != nil {
					return
				}
				buf := make([]byte, bytesToRead)
				offset := int64(idx) * int64(chunkSize)
				n, readErr := f.ReadAt(buf, offset)
				if readErr != nil && !(readErr == io.EOF && n > 0) {
					lastErr = fmt.Errorf("读取分片失败: %w", readErr)
					log.Printf("[RETRY] 文件 %s 分片 %d 第 %d 次读取失败: %v\n", task.FileName, idx, attempt, readErr)
					time.Sleep(time.Duration(attempt) * chunkRetryBaseDelay)
					continue
				}
				if n == 0 {
					lastErr = fmt.Errorf("分片 %d 读取为空", idx)
					log.Printf("[RETRY] 文件 %s 分片 %d 第 %d 次读取为空\n", task.FileName, idx, attempt)
					time.Sleep(time.Duration(attempt) * chunkRetryBaseDelay)
					continue
				}

				partPath := filepath.Join(s.taskDir, fmt.Sprintf("%s.part%d", task.FileName, idx))
				if err := os.WriteFile(partPath, buf[:n], defaultFilePerm); err != nil {
					lastErr = fmt.Errorf("写入失败: %w", err)
					log.Printf("[RETRY] 文件 %s 分片 %d 第 %d 次写入失败: %v\n", task.FileName, idx, attempt, err)
					time.Sleep(time.Duration(attempt) * chunkRetryBaseDelay)
					continue
				}

				mu.Lock()
				task.UploadedChunks++
				task.Status = "running"
				task.Message = fmt.Sprintf("已上传 %d/%d 分片", task.UploadedChunks, task.TotalChunks)
				mu.Unlock()

				log.Printf("[UPLOAD] 文件 %s 分片 %d/%d 上传成功\n", task.FileName, idx+1, task.TotalChunks)
				lastErr = nil
				break
			}

			if lastErr != nil {
				mu.Lock()
				if writeErr == nil {
					writeErr = fmt.Errorf("分片 %d 最终写入失败: %v", idx, lastErr)
				}
				mu.Unlock()
			}
		})
	}

	wg.Wait()
	if ctx.Err() != nil {
		return context.Canceled
	}
	return writeErr
}

// 下载（支持多线程、断点续传、失败重传）
func (s *FileService) DownloadFile(fileName string, chunkSize int, savePath string) (*vo.FileTaskRequest, error) {
	srcPath := filepath.Join(s.taskDir, fileName)
	if !util.FileExists(srcPath) {
		return nil, fmt.Errorf("%w: %s", ErrFileNotFound, srcPath)
	}

	if savePath == "" {
		defaultDir := defaultDownloadDir
		_ = os.MkdirAll(defaultDir, defaultDirPerm)
		savePath = filepath.Join(defaultDir, fileName)
	}

	log.Printf("[DOWNLOAD] 准备下载文件 %s -> %s (chunk=%d)\n", srcPath, savePath, chunkSize)

	// 创建任务
	task := &vo.FileTaskRequest{
		FileName:         fileName,
		FileType:         taskTypeDownload,
		Status:           "pending",
		LocalPath:        savePath,
		DownloadedChunks: 0,
	}
	task.TaskID = dao.FileTaskDB.CreateTask(task)
	log.Printf("[DOWNLOAD] 创建下载任务 taskID=%d 文件=%s\n", task.TaskID, fileName)

	ctx, cancel := context.WithCancel(context.Background())
	s.registerCancel(task.TaskID, cancel)
	defer func() {
		cancel()
		s.unregisterCancel(task.TaskID)
	}()

	f, err := os.Open(srcPath)
	if err != nil {
		task.Status = "failed"
		task.Message = fmt.Sprintf("打开源文件失败: %v", err)
		return task, err
	}
	defer f.Close()

	stat, _ := f.Stat()
	fileSize := stat.Size()
	totalChunks := int((fileSize + int64(chunkSize) - 1) / int64(chunkSize))
	task.TotalChunks = totalChunks

	// 创建保存文件目录
	_ = os.MkdirAll(filepath.Dir(savePath), defaultDirPerm)

	// 临时分片路径
	partDir := filepath.Join(s.taskDir, downloadTmpRoot, fileName)
	_ = os.MkdirAll(partDir, defaultDirPerm)

	task.Status = "running"
	task.Message = "开始下载"
	log.Printf("[DOWNLOAD] 任务 %d 开始分片下载，总分片=%d\n", task.TaskID, totalChunks)

	// 并行下载
	err = s.downloadChunks(ctx, f, totalChunks, chunkSize, partDir, task)
	if err != nil {
		if errors.Is(err, context.Canceled) {
			task.Status = "cancelled"
			task.Message = "任务已被取消"
		} else {
			task.Status = "failed"
			task.Message = fmt.Sprintf("分片下载失败: %v", err)
			log.Printf("[DOWNLOAD][ERROR] 任务 %d 分片下载失败: %v\n", task.TaskID, err)
		}
		return task, err
	}

	// 合并分片
	partFn := func(idx int) string {
		return filepath.Join(partDir, fmt.Sprintf("%s.part%d", fileName, idx))
	}
	if err := mergePartFiles(totalChunks, partFn, savePath, false); err != nil {
		task.Status = "failed"
		task.Message = "合并文件失败"
		log.Printf("[DOWNLOAD][ERROR] 任务 %d 合并文件失败: %v\n", task.TaskID, err)
		return task, err
	}

	task.Status = "success"
	task.Message = fmt.Sprintf("下载完成，保存到: %s", savePath)
	log.Printf("[DOWNLOAD] 任务 %d 完成，目标文件 %s\n", task.TaskID, savePath)
	return task, nil
}

// downloadChunks 并行下载分片 并实现失败重试
func (s *FileService) downloadChunks(ctx context.Context, f *os.File, totalChunks, chunkSize int, partDir string, task *vo.FileTaskRequest) error {
	var wg sync.WaitGroup
	var mu sync.Mutex
	var failed error
	manager := GetTaskManager()

	for i := 0; i < totalChunks; i++ {
		if ctx.Err() != nil {
			break
		}
		idx := i
		wg.Add(1)
		manager.RunTask(func() {
			defer wg.Done()
			if ctx.Err() != nil {
				return
			}

			partPath := filepath.Join(partDir, fmt.Sprintf("%s.part%d", task.FileName, idx))
			if util.FileExists(partPath) {
				mu.Lock()
				task.DownloadedChunks++
				task.Message = fmt.Sprintf("跳过已存在分片 %d/%d", task.DownloadedChunks, task.TotalChunks)
				mu.Unlock()
				log.Printf("[DOWNLOAD] 任务 %d 分片 %d/%d 已存在，跳过\n", task.TaskID, idx+1, task.TotalChunks)
				return
			}

			retries := chunkRetryAttempts
			for attempt := 1; attempt <= retries; attempt++ {
				if ctx.Err() != nil {
					return
				}
				if err := s.downloadChunk(f, idx, chunkSize, partPath); err != nil {
					if attempt == retries {
						mu.Lock()
						if failed == nil {
							failed = fmt.Errorf("分片 %d 下载失败: %v", idx, err)
						}
						mu.Unlock()
						log.Printf("[DOWNLOAD][ERROR] 任务 %d 分片 %d 最终失败: %v\n", task.TaskID, idx+1, err)
					} else {
						log.Printf("[DOWNLOAD][RETRY] 任务 %d 分片 %d 第 %d 次失败: %v\n", task.TaskID, idx+1, attempt, err)
						time.Sleep(chunkRetryBaseDelay)
						continue
					}
				} else {
					mu.Lock()
					task.DownloadedChunks++
					task.Message = fmt.Sprintf("已下载 %d/%d 分片", task.DownloadedChunks, task.TotalChunks)
					mu.Unlock()
					log.Printf("[DOWNLOAD] 任务 %d 分片 %d/%d 下载成功\n", task.TaskID, idx+1, task.TotalChunks)
					break
				}
			}
		})
	}

	wg.Wait()
	if ctx.Err() != nil {
		return context.Canceled
	}
	return failed
}

// downloadFullFile 当远程的服务器不支持range切片 修改成 单线程下载整个文件
func downloadFullFile(ctx context.Context, url string, file *os.File) error {
	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		return err
	}
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
			return context.Canceled
		}
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("整文件下载失败，状态码: %d", resp.StatusCode)
	}

	if err := file.Truncate(0); err != nil {
		return fmt.Errorf("重置目标文件失败: %w", err)
	}
	if _, err := file.Seek(0, io.SeekStart); err != nil {
		return fmt.Errorf("定位文件开头失败: %w", err)
	}

	if _, err := io.Copy(file, resp.Body); err != nil {
		if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
			return context.Canceled
		}
		return fmt.Errorf("整文件写入失败: %w", err)
	}
	return nil
}

// downloadChunk 下载单个分片
func (s *FileService) downloadChunk(f *os.File, idx, chunkSize int, partPath string) error {
	buf := make([]byte, chunkSize)
	offset := int64(idx) * int64(chunkSize)

	_, err := f.Seek(offset, io.SeekStart)
	if err != nil {
		return fmt.Errorf("Seek 失败: %v", err)
	}

	n, err := f.Read(buf)
	if err != nil && err != io.EOF {
		return fmt.Errorf("读取分片失败: %v", err)
	}

	return os.WriteFile(partPath, buf[:n], defaultFilePerm)
}

// mergePartFiles 合并分片为完整文件
func mergePartFiles(total int, partPathFn func(idx int) string, finalPath string, deleteParts bool) error {
	out, err := os.Create(finalPath)
	if err != nil {
		return err
	}
	defer out.Close()

	for i := 0; i < total; i++ {
		partPath := partPathFn(i)
		partData, err := os.ReadFile(partPath)
		if err != nil {
			return fmt.Errorf("读取分片失败: %v", err)
		}
		if len(partData) > 0 {
			if _, err := out.Write(partData); err != nil {
				return fmt.Errorf("写入合并文件失败: %v", err)
			}
		}
		if deleteParts {
			_ = os.Remove(partPath)
		}
	}
	return nil
}

// ListTasks 获取所有任务列表
func (s *FileService) ListTasks() []*vo.FileTaskRequest {
	log.Println("[INFO] 获取任务列表")

	tasks := dao.FileTaskDB.ListTasks()
	log.Printf("[INFO] 当前共有 %d 个任务\n", len(tasks))

	return tasks
}

// GetTask 获取指定任务详情
func (s *FileService) GetTask(taskID int64) (*vo.FileTaskRequest, error) {
	log.Printf("[INFO] 查询任务详情：taskID=%d\n", taskID)

	task, err := dao.FileTaskDB.GetTask(taskID)
	if err != nil {
		log.Printf("[ERROR] 查询任务失败：%v\n", err)
		return nil, fmt.Errorf("%w: %v", ErrTaskNotFound, err)
	}

	log.Printf("[INFO] 成功查询任务：%s 状态=%s\n", task.FileName, task.Status)
	return task, nil
}

// ListUploadedFiles 展示文件列表
func (s *FileService) ListUploadedFiles(page, pageSize int) (*vo.ListFilesResponse, error) {
	if page <= 0 {
		page = 1
	}
	if pageSize <= 0 {
		pageSize = 10
	}

	entries, err := os.ReadDir(s.taskDir)
	if err != nil {
		return nil, fmt.Errorf("读取上传目录失败: %w", err)
	}

	// 构建文件列表
	files := []vo.FileInfo{}
	for _, entry := range entries {
		if entry.Type().IsRegular() {
			// 获取文件大小
			filePath := filepath.Join(s.taskDir, entry.Name())
			stat, _ := os.Stat(filePath)
			files = append(files, vo.FileInfo{
				Name: entry.Name(),
				Size: stat.Size(),
			})
		}
	}

	total := len(files)
	start := (page - 1) * pageSize
	if start >= total {
		return &vo.ListFilesResponse{
			Total: total, Page: page, PageSize: pageSize, Files: []vo.FileInfo{},
		}, nil
	}

	end := start + pageSize
	if end > total {
		end = total
	}

	return &vo.ListFilesResponse{
		Total:    total,
		Page:     page,
		PageSize: pageSize,
		Files:    files[start:end],
	}, nil
}

// UpdateFile 更新文件名称
func (s *FileService) UpdateFile(req vo.UpdateFileRequest) error {
	oldPath := filepath.Join("upload", req.OldFileName)
	newPath := filepath.Join("upload", req.NewFileName)

	// 检查文件是否存在
	if !util.FileExists(oldPath) {
		return fmt.Errorf("%w: %s", ErrFileNotFound, req.OldFileName)
	}

	// 检查新文件名是否已经存在
	if util.FileExists(newPath) {
		return fmt.Errorf("%w: 目标文件已存在 %s", ErrInvalidParams, req.NewFileName)
	}

	// 执行重命名
	if err := os.Rename(oldPath, newPath); err != nil {
		return fmt.Errorf("文件重命名失败: %w", err)
	}

	// 如果任务记录里有这个文件名则同步更新
	for _, task := range dao.FileTaskDB.ListTasks() {
		if task.FileName == req.OldFileName && task.FileType == taskTypeUpload {
			task.FileName = req.NewFileName
			dao.FileTaskDB.UpdateTask(task.TaskID, task.Status, fmt.Sprintf("文件已重命名为 %s", req.NewFileName))
		}
	}

	return nil
}

// DeleteTask 删除任务
func (s *FileService) DeleteTask(taskID int64) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	task, err := dao.FileTaskDB.GetTask(taskID)
	if err != nil {
		return fmt.Errorf("%w: %v", ErrTaskNotFound, err)
	}

	log.Printf("[INFO] 删除任务：taskID=%d 文件=%s 类型=%s\n", taskID, task.FileName, task.FileType)

	task.Status = "cancelled"
	task.Message = "任务已被删除"

	s.cancelTask(taskID)

	switch task.FileType {
	case taskTypeUpload:
		s.CleanupUploadFiles(task)
	case taskTypeDownload:
		s.CleanupDownloadFiles(task)
	default:
		log.Printf("[WARN] 未识别的任务类型，跳过清理：%s\n", task.FileType)
	}

	dao.FileTaskDB.DeleteTaskByID(taskID)

	return nil
}

// DeleteUploadFile 删除物理文件
func (s *FileService) DeleteUploadFile(fileName string) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	filePath := filepath.Join("upload", fileName)
	if !util.FileExists(filePath) {
		return fmt.Errorf("%w: %s", ErrFileNotFound, fileName)
	}

	if err := util.DeleteFile(filePath); err != nil {
		return fmt.Errorf("文件删除失败: %w", err)
	}

	log.Printf("[INFO] 文件已成功删除：%s\n", filePath)
	return nil
}

// DownloadFromURLWithRange 多线程远程分片下载，断点续传，自动重试功能
func (s *FileService) DownloadFromURLWithRange(req vo.DownloadFromURLRequest) (*vo.FileTaskRequest, error) {
	if req.URL == "" {
		return nil, fmt.Errorf("%w: 下载URL不能为空", ErrInvalidParams)
	}
	if req.ChunkSize <= 0 {
		req.ChunkSize = DefaultChunkSize // 默认1MB
	}

	// 默认保存路径
	if req.SavePath == "" {
		_ = os.MkdirAll(defaultDownloadDir, defaultDirPerm)
		fileName := filepath.Base(req.URL)
		req.SavePath = filepath.Join(defaultDownloadDir, fileName)
	}

	// Step 1: 获取远程文件大小
	totalSize, err := getRemoteFileSize(req.URL)
	if err != nil {
		return nil, fmt.Errorf("无法获取文件大小: %w", err)
	}
	if totalSize <= 0 {
		return nil, fmt.Errorf("%w: 文件大小为0或无法获取", ErrInvalidParams)
	}
	totalChunks := int((totalSize + req.ChunkSize - 1) / req.ChunkSize)

	// Step 2: 创建任务
	task := &vo.FileTaskRequest{
		FileName:         filepath.Base(req.SavePath),
		FileType:         taskTypeDownload,
		Status:           "pending",
		TotalChunks:      totalChunks,
		DownloadedChunks: 0,
		LocalPath:        req.SavePath,
	}
	task.TaskID = dao.FileTaskDB.CreateTask(task)

	ctx, cancel := context.WithCancel(context.Background())
	s.registerCancel(task.TaskID, cancel)
	defer func() {
		cancel()
		s.unregisterCancel(task.TaskID)
	}()

	// Step 3: 准备保存路径
	saveDir := filepath.Dir(req.SavePath)
	if err := os.MkdirAll(saveDir, defaultDirPerm); err != nil {
		task.Status = "failed"
		task.Message = fmt.Sprintf("创建目录失败: %v", err)
		return task, err
	}

	// Step 4: 打开文件
	file, err := os.OpenFile(req.SavePath, os.O_CREATE|os.O_RDWR, defaultFilePerm)
	if err != nil {
		task.Status = "failed"
		task.Message = fmt.Sprintf("打开文件失败: %v", err)
		return task, err
	}
	defer file.Close()

	task.Status = "running"
	task.Message = "开始分片下载"

	// Step 5: 并行下载
	err = s.downloadChunksFromURL(ctx, req, totalSize, file, task)
	if err != nil {
		if errors.Is(err, context.Canceled) {
			task.Status = "cancelled"
			task.Message = "任务已被取消"
			return task, err
		}
		if errors.Is(err, ErrRangeNotSupported) {
			log.Printf("[DOWNLOAD][FALLBACK] 远程资源不支持 Range，切换为单线程下载：%s\n", req.URL)
			if err := downloadFullFile(ctx, req.URL, file); err != nil {
				if errors.Is(err, context.Canceled) {
					task.Status = "cancelled"
					task.Message = "任务已被取消"
					return task, err
				}
				task.Status = "failed"
				task.Message = fmt.Sprintf("整文件下载失败: %v", err)
				return task, err
			}
			task.DownloadedChunks = task.TotalChunks
			dao.FileTaskDB.UpdateDownloadedChunks(task.TaskID, task.DownloadedChunks)
		} else {
			task.Status = "failed"
			task.Message = fmt.Sprintf("下载失败: %v", err)
			return task, err
		}
	}

	task.Status = "success"
	task.Message = "下载完成"
	dao.FileTaskDB.UpdateStatus(task.TaskID, task.Status)
	return task, nil
}

// downloadChunksFromURL 并行下载分片（带失败重试 + 断点续传）
func (s *FileService) downloadChunksFromURL(ctx context.Context, req vo.DownloadFromURLRequest, totalSize int64, file *os.File, task *vo.FileTaskRequest) error {
	var wg sync.WaitGroup
	var mu sync.Mutex
	var failed error
	manager := GetTaskManager()

	for i := 0; i < task.TotalChunks; i++ {
		if ctx.Err() != nil {
			break
		}
		start := int64(i) * req.ChunkSize
		end := start + req.ChunkSize - 1
		if end >= totalSize {
			end = totalSize - 1
		}

		idx := i
		chunkStart := start
		chunkEnd := end

		wg.Add(1)
		manager.RunTask(func() {
			defer wg.Done()
			if ctx.Err() != nil {
				return
			}

			// 检查已有分片（断点续传）
			buf := make([]byte, 1)
			n, _ := file.ReadAt(buf, chunkStart)
			if n > 0 {
				mu.Lock()
				task.DownloadedChunks++
				task.Message = fmt.Sprintf("跳过已存在分片 %d/%d", task.DownloadedChunks, task.TotalChunks)
				dao.FileTaskDB.UpdateDownloadedChunks(task.TaskID, task.DownloadedChunks)
				mu.Unlock()
				return
			}

			retries := chunkRetryAttempts
			for attempt := 1; attempt <= retries; attempt++ {
				if ctx.Err() != nil {
					return
				}
				if err := downloadRangeSafe(ctx, req.URL, chunkStart, chunkEnd, file); err != nil {
					if errors.Is(err, ErrRangeNotSupported) {
						mu.Lock()
						if !errors.Is(failed, ErrRangeNotSupported) {
							failed = ErrRangeNotSupported
						}
						mu.Unlock()
						return
					}
					if errors.Is(err, context.Canceled) {
						return
					}
					if attempt == retries {
						mu.Lock()
						if failed == nil {
							failed = fmt.Errorf("分片 %d 下载失败: %v", idx, err)
						}
						mu.Unlock()
					} else {
						time.Sleep(chunkRetryBaseDelay)
						continue
					}
				} else {
					mu.Lock()
					task.DownloadedChunks++
					task.Message = fmt.Sprintf("已下载 %d/%d 分片", task.DownloadedChunks, task.TotalChunks)
					dao.FileTaskDB.UpdateDownloadedChunks(task.TaskID, task.DownloadedChunks)
					mu.Unlock()
					break
				}
			}
		})
	}

	wg.Wait()
	if ctx.Err() != nil {
		return context.Canceled
	}
	return failed
}

// downloadRangeSafe 使用 HTTP Range 请求 下载远程文件的指定字节范围到本地文件中，用于分片下载
func downloadRangeSafe(ctx context.Context, url string, start, end int64, file *os.File) error {
	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		return err
	}
	req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", start, end))

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
			return context.Canceled
		}
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusPartialContent {
		if resp.StatusCode == http.StatusOK {
			return ErrRangeNotSupported
		}
		return fmt.Errorf("下载失败，状态码: %d", resp.StatusCode)
	}

	_, err = file.Seek(start, io.SeekStart)
	if err != nil {
		return err
	}

	written, err := io.Copy(file, resp.Body)
	if err != nil {
		if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
			return context.Canceled
		}
		return err
	}
	if written == 0 {
		return fmt.Errorf("分片为空: %d-%d", start, end)
	}
	return nil
}

// getRemoteFileSize 获取远程文件大小
func getRemoteFileSize(url string) (int64, error) {
	resp, err := http.Head(url)
	if err == nil && resp.ContentLength > 0 {
		return resp.ContentLength, nil
	}

	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Range", "bytes=0-0")
	getResp, err := http.DefaultClient.Do(req)
	if err != nil {
		return 0, err
	}
	defer getResp.Body.Close()

	contentRange := getResp.Header.Get("Content-Range")
	if strings.Contains(contentRange, "/") {
		parts := strings.Split(contentRange, "/")
		if len(parts) == 2 {
			size, _ := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64)
			return size, nil
		}
	}
	return getResp.ContentLength, nil
}
